
package com.tgh.utils;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLU;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.IOException;

public class GLShaderManager {
    
    public static final int GL_ATTRIBUTE_VERTEX=0;
    public static final int GL_ATTRIBUTE_COLOR=1;
    public static final int GL_ATTRIBUTE_NORMAL=2;
    public static final int GL_ATTRIBUTE_TEXTURE0=3;
    public static final int GL_ATTRIBUTE_TEXTURE1=4;
    public static final int GL_ATTRIBUTE_TEXTURE2=5;
    public static final int GL_ATTRIBUTE_TEXTURE3=6;
    
    public static enum ShaderType{
        Identity,FLAT,DEFAULT_LIGHT,POSITION_LIGHT,TEXTURE_REPLACE,TEXTURE_MODULATE,TEXTURE_POINT_LIGHT_DIFF
    }
    
    public boolean initStockShader() {
        uiStockShaders[ShaderType.Identity.ordinal()]=createProgram(IDENTITY_VERTICES_SHADER, IDENTITY_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX}, new String[]{"vVertex"});
        uiStockShaders[ShaderType.FLAT.ordinal()]=createProgram(FLAT_VERTICES_SHADER, FLAT_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX}, new String[]{"vVertex"});
        uiStockShaders[ShaderType.DEFAULT_LIGHT.ordinal()]=createProgram(DEFAULT_LIGHT_VERTICES_SHADER, DEFAULT_LIGHT_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX,GL_ATTRIBUTE_NORMAL}, new String[]{"vVertex","vNormal"});
        uiStockShaders[ShaderType.POSITION_LIGHT.ordinal()]=createProgram(POSITION_LIGHT_VERTICES_SHADER, POSITION_LIGHT_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX,GL_ATTRIBUTE_NORMAL}, new String[]{"vVertex","vNormal"});
        uiStockShaders[ShaderType.TEXTURE_REPLACE.ordinal()]=createProgram(TEXTURE_REPLACE_VERTICES_SHADER, TEXTURE_REPLACE_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX,GL_ATTRIBUTE_TEXTURE0}, new String[]{"vVertex","vTexCoord0"});
        uiStockShaders[ShaderType.TEXTURE_MODULATE.ordinal()]=createProgram(TEXTURE_MODULATE_VERTICES_SHADER, TEXTURE_MODULATE_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX,GL_ATTRIBUTE_TEXTURE0}, new String[]{"vVertex","vTexCoord0"});
        uiStockShaders[ShaderType.TEXTURE_POINT_LIGHT_DIFF.ordinal()]=createProgram(TEXTURE_POINT_LIGHT_DIFF_VERTICES_SHADER, TEXTURE_POINT_LIGHT_DIFF_FRAGMENT_SHADER, new int[]{GL_ATTRIBUTE_VERTEX,GL_ATTRIBUTE_NORMAL,GL_ATTRIBUTE_TEXTURE0}, new String[]{"vVertex","vNormal","vTexCoord0"});
        return uiStockShaders[0]!=0;
    }
    
    /**
     * Identity : vColor uiPointSize <p>
     * FLAT : mvpMatrix vColor uiPointSize<p>
     * DEFAULT_LIGHT : mvMatrix pMatrix vColor<p>
     * POSITION_LIGHT : mvMatrix pMatrix vLightPos vColor<p>
     * TEXTURE_REPLACE : mvpMatrix textureUnit0<p>
     * TEXTURE_MODULATE : mvpMatrix vColor textureUnit0<p>
     * TEXTURE_POINT_LIGHT_DIFF : mvMatrix pMatrix vLightPos vColor textureUnit0<p>
     * @param type
     * @param obj
     * @return
     */

    public int useStockShader(ShaderType type,Object ...obj) {
        
        int iColor, iTransform, iPointSize, iModelMatrix, iProjMatrix, iLightPos, iTextureUnit0;
        float[] vColor,vLightPos,mvpMatrix,mvMatrix,pMatrix;
        float pointSize;
        int iInteger;
        int program=uiStockShaders[type.ordinal()];
        GLES20.glUseProgram(program);
        
        switch (type) {
            case Identity:
                iColor= GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[0];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                
                if (obj.length>1) {
                    iPointSize=GLES20.glGetUniformLocation(program, "uiPointSize");
                    pointSize=(Float) obj[1];
                    GLES20.glUniform1f(iPointSize, pointSize);
                }
                break;
            case FLAT:
                iTransform= GLES20.glGetUniformLocation(program, "mvpMatrix");
                mvpMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iTransform, 1, false, mvpMatrix, 0);
                
                iColor= GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[1];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                
                if (obj.length>2) {
                    iPointSize=GLES20.glGetUniformLocation(program, "uiPointSize");
                    pointSize=(Float) obj[2];
                    GLES20.glUniform1f(iPointSize, pointSize);
                }
                break;
            case DEFAULT_LIGHT:
                iModelMatrix=GLES20.glGetUniformLocation(program, "mvMatrix");
                mvMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iModelMatrix, 1, false, mvMatrix, 0);
                
                iProjMatrix=GLES20.glGetUniformLocation(program, "pMatrix");
                pMatrix=(float[]) obj[1];
                GLES20.glUniformMatrix4fv(iProjMatrix, 1,false, pMatrix, 0);
                
                iColor=GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[2];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                break;
            case POSITION_LIGHT:
                iModelMatrix=GLES20.glGetUniformLocation(program, "mvMatrix");
                mvMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iModelMatrix, 1, false, mvMatrix, 0);
                
                iProjMatrix=GLES20.glGetUniformLocation(program, "pMatrix");
                pMatrix=(float[]) obj[1];
                GLES20.glUniformMatrix4fv(iProjMatrix, 1,false, pMatrix, 0);
                
                iLightPos=GLES20.glGetUniformLocation(program, "vLightPos");
                vLightPos=(float[]) obj[2];
                GLES20.glUniform3fv(iLightPos, 1, vLightPos, 0);
                
                iColor=GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[3];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                break;
            case TEXTURE_REPLACE:
                iTransform=GLES20.glGetUniformLocation(program, "mvpMatrix");
                mvpMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iTransform, 1, false, mvpMatrix, 0);
                
                iTextureUnit0=GLES20.glGetUniformLocation(program, "textureUnit0");
                iInteger=(Integer) obj[1];
                GLES20.glUniform1i(iTextureUnit0, iInteger);
                break;
            case TEXTURE_MODULATE:
                iTransform=GLES20.glGetUniformLocation(program, "mvpMatrix");
                mvpMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iTransform, 1, false, mvpMatrix, 0);
                
                iColor=GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[1];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                
                iTextureUnit0=GLES20.glGetUniformLocation(program, "textureUnit0");
                iInteger=(Integer) obj[2];
                GLES20.glUniform1i(iTextureUnit0, iInteger);
                break;
            case TEXTURE_POINT_LIGHT_DIFF:
                iModelMatrix=GLES20.glGetUniformLocation(program, "mvMatrix");
                mvMatrix=(float[]) obj[0];
                GLES20.glUniformMatrix4fv(iModelMatrix, 1, false, mvMatrix, 0);
                
                iProjMatrix=GLES20.glGetUniformLocation(program, "pMatrix");
                pMatrix=(float[]) obj[1];
                GLES20.glUniformMatrix4fv(iProjMatrix, 1,false, pMatrix, 0);
                
                iLightPos=GLES20.glGetUniformLocation(program, "vLightPos");
                vLightPos=(float[]) obj[2];
                GLES20.glUniform3fv(iLightPos, 1, vLightPos, 0);
                
                iColor=GLES20.glGetUniformLocation(program, "vColor");
                vColor=(float[]) obj[3];
                GLES20.glUniform4fv(iColor, 1, vColor, 0);
                
                iTextureUnit0=GLES20.glGetUniformLocation(program, "textureUnit0");
                iInteger=(Integer) obj[4];
                GLES20.glUniform1i(iTextureUnit0, iInteger);
                break;
            default:
                break;
        }
        return program;
    }

    public void release() {
        for(int program: uiStockShaders){
            GLES20.glDeleteProgram(program);
        }
    }
    
    public static int createProgram(String vertexSource,String fragmentSource,int[] indicts,String[] attriNames) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader==0) {
            return 0;
        }
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (fragmentShader==0) {
            return 0;
        }
        int program = GLES20.glCreateProgram();
        if (program==0) {
            return 0;
        }
        GLES20.glAttachShader(program, vertexShader);
        checkGlError("glAttachShader");
        
        GLES20.glAttachShader(program, fragmentShader);
        checkGlError("glAttachShader");
        
        if (indicts.length!=attriNames.length) {
            throw new RuntimeException("attribute的序号和名字不匹配");
        }
        
        for(int i=0;i<indicts.length;i++){
            GLES20.glBindAttribLocation(program, indicts[i], attriNames[i]);
        }
        
        GLES20.glLinkProgram(program);
        GLES20.glDeleteShader(vertexShader);
        GLES20.glDeleteShader(fragmentShader);
        
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
        if (linkStatus[0] != GLES20.GL_TRUE) 
        {
            Log.e(TAG, "Could not link program: ");
            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
            GLES20.glDeleteProgram(program);
            return 0;
        }
        return program;
    }
    
    public static int loadShader(int shaderType,String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader!=0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled=new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0]==0) {
                Log.e(TAG, "Could not compile shader " + shaderType + ":");
                checkGlError("loadShader");
                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader=0;
            }
        }
        return shader;
    }
    
    public static String readFileFromAssets(Context context,String fileName){
        try {
            BufferedInputStream stream = new BufferedInputStream(context.getAssets().open(fileName));
            StringBuilder result=new StringBuilder();
            byte[] buffer=new byte[1024];
            int len=-1;
            while ((len=stream.read(buffer))>0) {
                result.append(new String(buffer, 0, len));
            }
            stream.close();
            return result.toString();
        } catch (IOException e) {
            Log.i(TAG, e.getMessage());
        }
        return null;
    }
    
    private static void checkGlError(String op) 
    {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) 
        {
            Log.e(TAG, op + ": glError " + error+",message: "+GLU.gluErrorString(error));
            throw new RuntimeException(op + ": glError " + error);
        }
    }
    
    
    private int[] uiStockShaders=new int[ShaderType.values().length];
    
    private static final String TAG = "GLShaderManager";
    
    private static final String IDENTITY_VERTICES_SHADER = 
             "attribute vec4 vVertex;"+ 
             "uniform float uiPointSize;"+ 
             "void main() { " + 
              " gl_Position = vVertex;" +
              " gl_PointSize = uiPointSize;" +
             "} ";
    
    private static final String IDENTITY_FRAGMENT_SHADER =
            "precision mediump float;"+
            "uniform vec4 vColor;" +
            "void main(){" +
            "gl_FragColor = vColor;" +
            "}";
    
    private static final String FLAT_VERTICES_SHADER = 
            "attribute vec4 vVertex;"+ 
            "uniform float uiPointSize;"+ 
            "uniform mat4 mvpMatrix;"+ 
            "void main() {" + 
            " gl_Position =mvpMatrix* vVertex;" +
            " gl_PointSize = uiPointSize;" +
            "}";


    private static final String FLAT_FRAGMENT_SHADER =
            "precision mediump float;"+
            "uniform vec4 vColor;" +
            "void main(){" +
            "gl_FragColor = vColor;" +
            "}";
    
    private static final String DEFAULT_LIGHT_VERTICES_SHADER=
            "uniform mat4 mvMatrix;" +
            "uniform mat4 pMatrix;" +
            "varying vec4 vFragColor;" +
            "attribute vec4 vVertex;" +
            "attribute vec3 vNormal;" +
            "uniform vec4 vColor;" +
            "void main(void) { " +
            " mat3 mNormalMatrix;" +
            " mNormalMatrix[0] = mvMatrix[0].xyz;" +
            " mNormalMatrix[1] = mvMatrix[1].xyz;" +
            " mNormalMatrix[2] = mvMatrix[2].xyz;" +
            " vec3 vNorm = normalize(mNormalMatrix * vNormal);" +
            " vec3 vLightDir = vec3(0.0, 0.0, 1.0); " +
            " float fDot = max(0.0, dot(vNorm, vLightDir)); " +
            " vFragColor.rgb = vColor.rgb * fDot;" +
            " vFragColor.a = vColor.a;" +
            " mat4 mvpMatrix;" +
            " mvpMatrix = pMatrix * mvMatrix;" +
            " gl_Position = mvpMatrix * vVertex; " +
            "}";
    
    private static final String DEFAULT_LIGHT_FRAGMENT_SHADER=
            "precision mediump float;" +
            "varying vec4 vFragColor;" +
            "void main(void) {  " +
            " gl_FragColor = vFragColor;" +
            "}";
    
    private static final String POSITION_LIGHT_VERTICES_SHADER = 
            "uniform mat4 mvMatrix;"+
            "uniform mat4 pMatrix;"+
            "uniform vec3 vLightPos;"+
            "uniform vec4 vColor;"+
            "attribute vec4 vVertex;"+
            "attribute vec3 vNormal;"+
            "varying vec4 vFragColor;"+
            "void main() { "+
            " mat3 mNormalMatrix;"+
            " mNormalMatrix[0] = normalize(mvMatrix[0].xyz);"+
            " mNormalMatrix[1] = normalize(mvMatrix[1].xyz);"+
            " mNormalMatrix[2] = normalize(mvMatrix[2].xyz);"+
            " vec3 vNorm = normalize(mNormalMatrix * vNormal);"+
            " vec4 ecPosition;"+
            " vec3 ecPosition3;"+
            " ecPosition = mvMatrix * vVertex;"+
            " ecPosition3 = ecPosition.xyz /ecPosition.w;"+
            " vec3 vLightDir = normalize(vLightPos - ecPosition3);"+
            " float fDot = max(0.0, dot(vNorm, vLightDir)); "+
            " vFragColor.rgb = vColor.rgb * fDot;"+
            " vFragColor.a = vColor.a;"+
            " mat4 mvpMatrix;"+
            " mvpMatrix = pMatrix * mvMatrix;"+
            " gl_Position = mvpMatrix * vVertex; "+
            "}";

    private static final String POSITION_LIGHT_FRAGMENT_SHADER  = 
          "precision mediump float;"+
          "varying vec4 vFragColor; "+
          "void main() { "+
          " gl_FragColor = vFragColor; "+
          "}";
    
    private static final String TEXTURE_REPLACE_VERTICES_SHADER =
            "uniform mat4 mvpMatrix;"+
             "attribute vec4 vVertex;" + 
             "attribute vec2 vTexCoord0;" +
             "varying vec2 vTex;"+
             "void main() {" +
             " vTex = vTexCoord0;" +
             " gl_Position = mvpMatrix * vVertex; "+
             "}";
                                     
    private static final String TEXTURE_REPLACE_FRAGMENT_SHADER =
            "precision mediump float;"+
            "varying vec2 vTex;" +
            "uniform sampler2D textureUnit0;" +
            "void main() {" +
            " gl_FragColor = texture2D(textureUnit0, vTex); " +
            "}";
    

    private static final String TEXTURE_MODULATE_VERTICES_SHADER =
            "uniform mat4 mvpMatrix;" +
            "attribute vec4 vVertex;" +
            "attribute vec2 vTexCoord0;" +
            "varying vec2 vTex;"+
            "void main() {" +
            " vTex = vTexCoord0;" +
            " gl_Position = mvpMatrix * vVertex; "+
            "}";
                                      
    private static final String TEXTURE_MODULATE_FRAGMENT_SHADER =
            "precision mediump float;"+
            "varying vec2 vTex;" +
            "uniform sampler2D textureUnit0;" +
            "uniform vec4 vColor;"+
            "void main() {" +
            " gl_FragColor = vColor * texture2D(textureUnit0, vTex); "+
            "}";
    
    private static final String TEXTURE_POINT_LIGHT_DIFF_VERTICES_SHADER= 
            "uniform mat4 mvMatrix;"+
            "uniform mat4 pMatrix;"+
            "uniform vec3 vLightPos;"+
            "uniform vec4 vColor;"+
            "attribute vec4 vVertex;"+
            "attribute vec3 vNormal;"+
            "attribute vec2 vTexCoord0;"+
            "varying vec4 vFragColor;"+
            "varying vec2 vTex;"+
            "void main() { "+
            " mat3 mNormalMatrix;"+
            " mNormalMatrix[0] = normalize(mvMatrix[0].xyz);"+
            " mNormalMatrix[1] = normalize(mvMatrix[1].xyz);"+
            " mNormalMatrix[2] = normalize(mvMatrix[2].xyz);"+
            " vec3 vNorm = normalize(mNormalMatrix * vNormal);"+
            " vec4 ecPosition;"+
            " vec3 ecPosition3;"+
            " ecPosition = mvMatrix * vVertex;"+
            " ecPosition3 = ecPosition.xyz /ecPosition.w;"+
            " vec3 vLightDir = normalize(vLightPos - ecPosition3);"+
            " float fDot = max(0.0, dot(vNorm, vLightDir)); "+
            " vFragColor.rgb = vColor.rgb * fDot;"+
            " vFragColor.a = vColor.a;"+
            " vTex = vTexCoord0;"+
            " mat4 mvpMatrix;"+
            " mvpMatrix = pMatrix * mvMatrix;"+
            " gl_Position = mvpMatrix * vVertex; "+
            "}";


    private static final String TEXTURE_POINT_LIGHT_DIFF_FRAGMENT_SHADER =  
          "precision mediump float;"+
          "varying vec4 vFragColor;"+
          "varying vec2 vTex;"+
          "uniform sampler2D textureUnit0;"+
          "void main() { "+
          " gl_FragColor = vFragColor * texture2D(textureUnit0, vTex);"+
          "}";

}